package org.ecpkn.PAGridSim.core.gridsimEntity.paMachine;

import gridsim.Machine;
import gridsim.ResGridletList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.ecpkn.PAGridSim.core.configuration.Configuration;
import org.ecpkn.PAGridSim.core.report.ExecutionTraceHashMap;
import org.ecpkn.PAGridSim.core.tasksCreation.Job;
import org.ecpkn.PAGridSim.core.tasksCreation.Task;

/**
 * A new type of machine aware to energy consumption
 * 
 * @author yyd
 * 
 */

public class PAMachine extends Machine {
	public static final class Const {
		// Instructions per Clock, to be simple, we set it to be 1
		public static final double IPC = 1.0;
		public static int STATE_ON = 1;
		public static int STATE_OFF = 0;
	}

	// Two parameter in P = c * f ^ 3 + Ps
	// Default value
	public static double POWERSTATIQUE;
	public static double COEFFICIENT;
	public static double MAXRATING;
	static {
		POWERSTATIQUE = Configuration.getInstance().getParameterDouble(
				Configuration.Const.POWER_STATIQUE);
		COEFFICIENT = Configuration.getInstance().getParameterDouble(
				Configuration.Const.POWER_COEFFICIENT);
		MAXRATING = Configuration.getInstance().getParameterDouble(
				Configuration.Const.MAX_RATING);
	}

	// Frequency of PE
	protected double frequencyPE_;
	// taskIds used in a partition policy
	private ArrayList<Task> tasksInMachine_;
	// TODO:limit the code who touches the utilization to make sure that the
	// history is correct
	// total utilization made by tasks
	protected Double machinesUtilization_;
	// turn on machine by default
	private int state = Const.STATE_OFF;
	private Map<Double, PAMachineStatut> history;
	// HashMap<gridletID,singleExecutionTrace>
	private ExecutionTraceHashMap executionTraceHashMap;

	/**
	 * Use constructor in class Machine to construct a PAMachine And it uses
	 * default ps and c
	 * 
	 * @param id
	 *            id of PAMachine 每个机器的ID
	 * @param numPE
	 *            number of Process Element 处理单元个数
	 * @param ratingPE
	 *            MIPS rating of PE 处理单元的MIPS
	 * 
	 */
	public PAMachine(int id, int numPE, int ratingPE) {
		super(id, numPE, ratingPE);
		history = new HashMap<Double, PAMachineStatut>();
		frequencyPE_ = ratingPE / Const.IPC;
		// this.powerHistory = new ArrayList<Double>();
		tasksInMachine_ = new ArrayList<Task>();
		machinesUtilization_ = 0.0;
		// TODO:should we begin with f=0 or max or standard??
		history.put(0.0, new PAMachineStatut(frequencyPE_,
				machinesUtilization_, Const.STATE_OFF));
		this.executionTraceHashMap = new ExecutionTraceHashMap();
	}

	public double getFrequencyPE_() {
		return frequencyPE_;
	}

	public ArrayList<Task> getTasksInMachine() {
		return tasksInMachine_;
	}

	public Double getMachinesUtilization() {
		return machinesUtilization_;
	}

	public Map<Double, PAMachineStatut> getHistory() {
		return history;
	}

	public ExecutionTraceHashMap getExecutionTraceHashMap() {
		return executionTraceHashMap;
	}

	/**
	 * adapt to different model, can be override
	 * 
	 * @return
	 */
	public double getPower() {
		return getPower(frequencyPE_, machinesUtilization_);
	}

	/**
	 * adapt to different model, can be override
	 * 
	 * @return
	 */
	public double getPower(double frequency, double utilization) {
		// Power not related to utilization
		// Power = Coefficient * frequency ^3 + PowerStatique
		double power = getNumPE()
				* (POWERSTATIQUE + COEFFICIENT * frequency * frequency
						* frequency);
		return power;
	}

	public void setTasksInMachine(ArrayList<Task> tasks) {
		tasksInMachine_ = tasks;
	}

	public void setFrequency(double timePoint, double frequency)
			throws Exception {
		setFrequency(frequency, null);
	}

	/**
	 * this method should be called in the place of setMIPSRating, in order to
	 * change the frequency at the same time the frequency is used for the
	 * calculation of power, but the GridSim only know MIPSRating
	 * 
	 * @param frequency
	 * @param gridletInExecList_
	 * @throws Exception
	 */
	public void setFrequency(double frequency, ResGridletList gridletInExecList_)
			throws Exception {
		// update Machine Utilization and check if need to migrate task
		// TODO:with the actual policy,in case that the utilization is greater
		// than 1,we make the frequency a little greater,so the following check
		// is useless
		boolean needToMigrateTask = updatUCheckNeedToMigrateTask(frequency);
		frequencyPE_ = (int) frequency + 1;
		for (int i = 0; i < getNumPE(); i++) {
			getPEList().get(i).setMIPSRating((int) (frequencyPE_ * Const.IPC));
		}
		if (needToMigrateTask) {
			deleteATaskId(gridletInExecList_);
		}
	}

	private void deleteATaskId(ResGridletList gridletInExecList_)
			throws Exception {
		Task taskToBeDelete;
		if (gridletInExecList_ != null) {
			for (int i = getTasksInMachine().size() - 1; i >= 0; i--) {
				taskToBeDelete = getTasksInMachine().get(i);
				boolean authorizedFlag = true;
				for (int index = 0; index < gridletInExecList_.size(); index++) {
					Job j = (Job) gridletInExecList_.get(index).getGridlet();
					if (j.getTaskID_() == taskToBeDelete.getTaskID_()) {
						authorizedFlag = false;
						break;
					}
				}
				if (authorizedFlag == true) {
					getTasksInMachine().remove(i);
					changeSingleTaskMachineUtilization(-taskToBeDelete
							.getLength_()
							/ getMIPSRating()
							/ taskToBeDelete.getPeriod_());
					if (machinesUtilization_ < 1) {
						break;
					}
				}
			}
		} else {
			throw new Exception(
					"PAMachine.deleteATaskId:Machine Utilization great than 1");
		}

	}

	/**
	 * if utilization >1, we should take some task to another machine
	 * 
	 * @param frequency
	 * @return
	 */
	public boolean updatUCheckNeedToMigrateTask(double frequency) {
		if (machinesUtilization_ == 0 && frequency == 0
				&& this.getTasksInMachine().size() == 0) {
			return false;
		} else {
			machinesUtilization_ = machinesUtilization_ * frequencyPE_
					/ frequency;
			return machinesUtilization_ > 1 ? true : false;
		}
	}

	public void changeSingleTaskMachineUtilization(Double machinesUtilization) {
		// TODO:merge add task and updateUtilization
		// TODO:utilization may also decrease
		machinesUtilization_ = machinesUtilization_ + machinesUtilization;
	}

	public void updateMachineUtilization(double timePoint) throws Exception {
		machinesUtilization_ = 0.0;
		for (Task t : tasksInMachine_) {
			machinesUtilization_ = machinesUtilization_ + t.getLength_()
					/ getMIPSRating() / t.getPeriod_();
		}
		if (machinesUtilization_ > 1) {
			setFrequency(timePoint, getFrequencyPE_() * (machinesUtilization_));
		}

	}

	public void addTaskToMachine(Task task) {
		// TODO:merge add task and update Utilization
		tasksInMachine_.add(task);
	}

	public void reset() {
		this.machinesUtilization_ = 0.0;
		this.tasksInMachine_.clear();
	}

	public boolean isTurnedOn() {
		return state == Const.STATE_ON;
	}

	public void turnOn() {
		this.state = Const.STATE_ON;
	}

	public void turnOff() {
		this.state = Const.STATE_OFF;
	}

	public void record(double timePoint) {
		history.put(timePoint, new PAMachineStatut(frequencyPE_,
				machinesUtilization_, this.state));
	}
}